/**
 * 
 */
SECTION_ELEMENT_HANDLERS = {
	'element':		new ElementHandler(),
	'loop':			new LoopHandler(),
	'expression':	new ExpressionHandler(),
	'condition':	new ConditionHandler()
};

function renderNode(tagNode, targetNode) {
	if (tagNode.nodeType == 3) { // text node
		$(targetNode).append(document.createTextNode(tagNode.nodeValue));
	} else if (tagNode.nodeType == 1) { // element node
		if (tagNode.nodeName == 'tag') {
			var type = tagNode.getAttribute('name');
			var handler = SECTION_ELEMENT_HANDLERS[type];
			if (!handler) {
				alert('no handler for tag "' + type + '"');
				return;
			}
			
			var map = {};
			switch(tagNode.getAttribute('name')) {
			case 'element':
			case 'expression':
				map.value = $(tagNode).children('parameter').attr('value');
				break;
			case 'loop':
				map.value = $(tagNode).children('parameter').text();
				map.children = $(tagNode).children('body')[0].childNodes;
				break;
			case 'condition':
				map.value = $(tagNode).children('parameter').text();
				map.children = $(tagNode).children('ifbody')[0].childNodes;
				break;
			}
			
			handler.render(targetNode, map);
		} else if (tagNode.nodeName == 'br') {
			insertNodeOverSelection($('<br/>')[0], targetNode);
		}
	}
}

function getInnerString(htmlNode) {
	var children = htmlNode.childNodes;
	var str = '';
	for (var i = 0; i < children.length; i++) {
		str += getNodeString(children[i]);
	}
	return str;
}

function getNodeString(htmlNode) {
	if (htmlNode.nodeType == 3) {
		var text = htmlNode.nodeValue;
		text = text.replace(/>/g, '&gt;');
		text = text.replace(/</g, '&lt;');
		text = text.replace(/"/g, '&quot;');
		return text;
	} else if (htmlNode.nodeType == 1) {
		var type = htmlNode.getAttribute('type');
		var handler = SECTION_ELEMENT_HANDLERS[type];
		
		if (handler) {
			return handler.getString(htmlNode);
		} else {
			switch(htmlNode.nodeName.toUpperCase()) {
			case 'BR':
				return '<br/>';
			}
		}
	}
	return '';
}

function ElementHandler() {
	this.render = function(targetNode, map) {
		if (!map.value) {
			alert('no value for element');
			return;
		}
		
		var parent = getInsertTarget(targetNode).parentNode;
		var type = $(parent).attr('type');
		if (type == 'loop') {
			var loopExpression = $(parent).attr('expression');
			// find out if added element is descendant of loop element
			if (map.value.indexOf(loopExpression) != 0) {
				alert('element ' + map.value + ' cannot be dropped in content of loop ' + loopExpression);
				return;
			} else {
				var index = loopExpression.length + 1;
				map.value = map.value.substring(index);
			}
		}
		var node = $('<span class="element" contenteditable="false" '
				+ 'type="element" expression="' + map.value + '"><b>'
				+ map.value + '</b></span>')[0];
		insertNodeOverSelection(node, targetNode);
	}
	
	this.getString = function(htmlnode) {
		var value = $(htmlnode).attr('expression');
		var str = '<tag name="element">';
		str += '<parameter name="expression" value="' + value + '" />';
		str += '</tag>';
		return str;
	}
}

function LoopHandler() {
	this.render = function(targetNode, map) {
		if (!map.value) {
			alert('no value for loop');
			return;
		}
		var parent = getInsertTarget(targetNode).parentNode;
		var type = $(parent).attr('type');
		if (type == 'loop') {
			var loopExpression = $(parent).attr('expression');
			// find out if added element is descendant of loop element
			if (map.value.indexOf(loopExpression) != 0) {
				alert('element ' + map.value + ' cannot be dropped in content of loop ' + loopExpression);
				return;
			} else {
				var index = loopExpression.length + 1;
				map.value = map.value.substring(index);
			}
		}
		var node = $('<span class="loop" contenteditable="false" '
				+ 'type="loop" expression="' + map.value + '"><b>'
				+ map.value + '</b>( '
				+ '<span class="loopBody droppable" contenteditable></span>'
				+ ' )</span>')[0];
		insertNodeOverSelection(node, targetNode);
		if (map.children) {
			var loopBody = $(node).children('SPAN')[0];
			for (var i = 0; i< map.children.length; i++) {
				renderNode(map.children[i], loopBody);
			}
		}
	}
	
	this.getString = function(htmlnode) {
		var value = $(htmlnode).attr('expression');
		var str = '<tag name="loop">';
		str += '<parameter name="loopExpression">' + value + '</parameter>';
		str += '<body>'; 
		var children = $(htmlnode).find('SPAN')[0].childNodes;
		for (var i = 0; i < children.length; i++) {
			str += getNodeString(children[i]);
		}
		str += '</body></tag>';
		return str;
	}
}

function ExpressionHandler() {
	this.render = function(targetNode, map) {
		if (!map.value) {
			map.value = '';
		}
		var node = $('<span class="expression" type="expression" contenteditable="false">'
				+ ' =<input type="text" value="' + map.value + '"/> '
				+ '</span>')[0];
		insertNodeOverSelection(node, targetNode);
	}
	
	this.getString = function(htmlnode) {
		var value = $(htmlnode).children('INPUT').val();
		var str = '<tag name="expression">';
		str += '<parameter name="expression" value="' + value + '" />';
		str += '</tag>';
		return str;
	}
}

function ConditionHandler() {
	this.render = function(targetNode, map) {
		if (!map.value) {
			map.value = '';
		}
		var node = $('<span class="condition" type="condition" contenteditable="false">'
				+ ' if <input type="text" value="' + map.value + '"/>'
				+ '<span class="conditionBody droppable" contenteditable></span>'
				+ '</span>')[0];
		insertNodeOverSelection(node, targetNode);
		if (map.children) {
			var loopBody = $(node).children('SPAN')[0];
			for (var i = 0; i< map.children.length; i++) {
				renderNode(map.children[i], loopBody);
			}
		}
	}
	
	this.getString = function(htmlnode) {
		var value = $(htmlnode).children('INPUT').val();
		var str = '<tag name="condition">';
		str += '<parameter name="condition">' + value + '</parameter>';
		str += '<ifbody>';
		var children = $(htmlnode).find('SPAN')[0].childNodes;
		for (var i = 0; i < children.length; i++) {
			str += getNodeString(children[i]);
		}
		str += '</ifbody><elsebody></elsebody></tag>';
		return str;
	}
}

function isOrContainsNode(ancestor, descendant) {
    var node = descendant;
    while (node) {
        if (node === ancestor) return true;
        node = node.parentNode;
    }
    return false;
}

function insertNodeOverSelection(node, containerNode) {
    var sel, range, html, parent;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            if (isOrContainsNode(containerNode, range.commonAncestorContainer)) {
            	parent = range.commonAncestorContainer;
                range.deleteContents();
                range.insertNode(node);
            } else {
            	parent = containerNode;
                containerNode.appendChild(node);
            }
        }
    } else if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        if (isOrContainsNode(containerNode, range.parentElement())) {
        	parent = range.parentElement();
            html = (node.nodeType == 3) ? node.data : node.outerHTML;
            range.pasteHTML(html);
        } else {
        	parent = containerNode;
            containerNode.appendChild(node);
        }
    }
    return parent;
}

function getInsertTarget(containerNode) {
    var sel, range, html, parent;
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            if (isOrContainsNode(containerNode, range.commonAncestorContainer)) {
            	parent = range.commonAncestorContainer;
            } else {
            	parent = containerNode;
            }
        }
    } else if (document.selection && document.selection.createRange) {
        range = document.selection.createRange();
        if (isOrContainsNode(containerNode, range.parentElement())) {
        	parent = range.parentElement();
        } else {
        	parent = containerNode;
        }
    }
    return parent;
}